#pragma once

#include "config.h"
#include "tunnel.h"
#include <unordered_map>


struct Rule {

  List joint;
  Ptr<Tunnel> tunnel;
  u32 srcIPv4 {0};
  u32 dstIPv4 {0};
  u16 srcPort {0};
  u16 dstPort {0};
  u16 port;

  inline static u64 srcKey (u32 srcIPv4, u16 srcPort) {
    return (u64)srcIPv4 | (u64)srcPort << 32;
  }

  inline static u32 ipv4FromKey (u64 key) {
    return (u32)key;
  }

  inline static u16 portFromKey (u64 key) {
    return (u16)(key >> 32);
  }
};


struct Router {

  inline void active (Rule* rule) {
    rule->joint.detach();
    _chain.push_back(&rule->joint);
  }

  inline void inactive (Rule* rule) {
    rule->joint.detach();
    _chain.push_front(&rule->joint);
  }


  explicit Router (const Config& cfg);

  ~Router ();

  void startup ();

  void shutdown ();

  void upLoop ();

  void downLoop ();

  void forwardUp (Ptr<Tunnel> tunnel, Ptr<IpPackage> pkg);

  bool forwardDown (Ptr<IpPackage> pkg);

  Rule* getRuleBySrc (Ptr<Tunnel> tunnel, u32 srcIPv4, u16 srcPort, u32 dstIPv4, u16 dstPort);

  Rule* getRuleByDst (u32 srcIPv4, u16 srcPort, u32 dstIPv4, u16 dstPort);

  void releaseMapping (Ptr<Tunnel> tunnel, u32 srcIPv4, u16 srcPort);

  void printRuleChain ();


private:
  const Config& _cfg;
  s32 _socket;
  u32 _ipv4;
  u32 _focus;

  Rule* _rules;
  List _chain;
  std::mutex _rules_mutex;
  std::unordered_map<u64, Rule*> _mapper;
  BlockQueue<IpPackage, &IpPackage::joint> _queue;
  std::mutex _cache_mutex;
  ByteBuffer<> _cache {IpPackage::MAX_SIZE};

  std::thread* _upWorker;
  std::thread* _downWorker;
};
